package com.nulldev.util.uri;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML.Tag;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;
import javax.swing.text.html.parser.ParserDelegator;

import com.nulldev.util.crypto.encryptAPI.manage.EncryptAPI;
import com.nulldev.util.error.ErrorCallback;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.other.CharsetUtil;
import com.nulldev.util.other.RandomUtils;
import com.nulldev.util.string.UnicodeUtils;
import com.nulldev.util.uri.URIUtil2.IO.Entry;
import com.nulldev.util.uri.http.HTTP;

public class URIUtil {

	public static String getDomainName(String url) throws Exception {
		try {
		    final URI uri = new URI(url);
		    final String domain = uri.getHost();
		    return domain.startsWith("www.") ? domain.substring(4) : domain;
		}catch (Exception e) {
			throw e;
		}
	}
	
	public static String getDomainName(final URL url) throws Exception {
		try {
		    final URL uri = url;
		    final String domain = uri.getHost();
		    return domain.startsWith("www.") ? domain.substring(4) : domain;
		}catch (Exception e) {
			throw e;
		}
	}

	public static void disableCertificateValidation() {
		System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2");
		  // Create a trust manager that does not validate certificate chains
		  TrustManager[] trustAllCerts = new TrustManager[] {
		  new X509TrustManager() {
		   public X509Certificate[] getAcceptedIssuers() {
			   return new X509Certificate[0];
		   }
		   public void checkClientTrusted(X509Certificate[] certs, String authType) {}
		   public void checkServerTrusted(X509Certificate[] certs, String authType) {}
		  }};
		  // Ignore differences between given hostname and certificate hostname
		  HostnameVerifier hv = new HostnameVerifier() {
			  public boolean verify(String hostname, SSLSession session) { 
				  return true; 
			  }
		  };
		  // Install the all-trusting trust manager
		  try {
		  SSLContext sc = SSLContext.getInstance("SSL");
		  sc.init(null, trustAllCerts, new RandomUtils.nSecureRandom());
		  HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
		  HttpsURLConnection.setDefaultHostnameVerifier(hv);
		  } catch (Exception e) {}
	}

	public static boolean isValid(final String url) {
		if (url == null) {
			return false;
		}
		try {
			final URL u = safeCreate(url);
			u.toURI();
			return true;
		}catch (Exception e) {
			return false;
		}
	}

	public static String downloadPage(final URL url) throws IOException {
		return downloadPage(url, true, Proxy.NO_PROXY, URIUtil2.URI_UTIL2_USER_AGENT);
	}
	
	public static String downloadPage(final URL url, final Charset charset) throws IOException {
		return downloadPage(url, true, Proxy.NO_PROXY, URIUtil2.URI_UTIL2_USER_AGENT, charset);
	}

	public static String downloadPage(final URL url, final boolean redirects) throws IOException {
		return downloadPage(url, redirects, Proxy.NO_PROXY, String.valueOf(MathUtil.nextRandomInt()));
	}
	
	public static String downloadPageF(final URL url, final boolean redirects, final Proxy proxy, final String useragent,
			final boolean keepLines) throws Exception {
		final HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
		conn.setFollowRedirects(redirects);
		conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
		conn.setRequestProperty("User-Agent", useragent);
		final String encoding = conn.getContentEncoding();
		InputStream inStr = null;
		
		if (!HTTP.isOK(conn.getResponseCode())) {
			inStr = conn.getErrorStream();
		}else {
			if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
			    inStr = new GZIPInputStream(conn.getInputStream());
			} else if (encoding != null && encoding.equalsIgnoreCase("deflate")) {
			    inStr = new InflaterInputStream(conn.getInputStream(),
			      new Inflater(true));
			} else {
			    inStr = conn.getInputStream();
			}
		}

		String out = (keepLines ? IOUtils.getStringFromInputStreamKeepNL(inStr) : 
			IOUtils.getStringFromInputStream(inStr));
		conn.disconnect();
		return out;
	}
	
	public static byte[] downloadPageFB(final URL url, final boolean redirects, final Proxy proxy, final String useragent) throws Exception {
		return downloadPageFB(url, redirects, proxy, useragent, false);
	}
	
	public static byte[] downloadPageFB(final URL url, final boolean redirects, final Proxy proxy, final String useragent,
			final boolean narrowTiming) throws Exception {
		return downloadPageFB(url, redirects, proxy, useragent, narrowTiming, false);
	}
	
	public static byte[] downloadPageFB(final URL url, final boolean redirects, final Proxy proxy, final String useragent,
			final boolean narrowTiming, final boolean isPOST, final Entry... entries) throws Exception {
		//JVMUtil.async_println("downloadPageFB(): " + url.toString());
		final HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
		if (isPOST) {
			conn.setRequestMethod("POST");
		}
		conn.setFollowRedirects(redirects);
		//conn.setRequestProperty("Host", getDomainName(url));
		conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
		conn.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
		conn.setRequestProperty("User-Agent", useragent);
		conn.setRequestProperty("Connection", "keep-alive");
		if (narrowTiming) {
			conn.setConnectTimeout(400);
		}
		boolean hasAcc = false; 
		for (final Entry ent : entries) {
			if (ent.getKey().equalsIgnoreCase("Accept")) {
				hasAcc = true;
			}
			conn.setRequestProperty(ent.getKey(), ent.getValue());
		}
		if (!hasAcc) {
			conn.setRequestProperty("Accept", "*/*");
		}
		final String encoding = conn.getContentEncoding();
		InputStream inStr = null;
		
		if (!HTTP.isOK(conn.getResponseCode())) {
			//JVMUtil.async_error("downloadPageFB(): Error: " + conn.getResponseCode());
			inStr = conn.getErrorStream();
		}else {
			if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
			    inStr = new GZIPInputStream(conn.getInputStream());
			} else if (encoding != null && encoding.equalsIgnoreCase("deflate")) {
			    inStr = new InflaterInputStream(conn.getInputStream(),
			      new Inflater(true));
			} else {
			    inStr = conn.getInputStream();
			}
		}
		//int len = conn.getContentLength();
		//if (len < 0) {
		//	len = 32;
		//}
		//byte[] out = new byte[len];
		//IOUtils.readFully(inStr, out);
		final ByteArrayOutputStream out = new ByteArrayOutputStream();
		IOUtils.copyLarge(inStr, out);
		conn.disconnect();
		return out.toByteArray();
	}
	
	public static InputStream downloadPageIS(final URL url, final boolean redirects, final Proxy proxy, final String useragent) throws Exception {
		final HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
		conn.setFollowRedirects(redirects);
		conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
		conn.setRequestProperty("User-Agent", useragent);
		final String encoding = conn.getContentEncoding();
		InputStream inStr = null;

		if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
		    inStr = new GZIPInputStream(conn.getInputStream());
		} else if (encoding != null && encoding.equalsIgnoreCase("deflate")) {
		    inStr = new InflaterInputStream(conn.getInputStream(),
		      new Inflater(true));
		} else {
		    inStr = conn.getInputStream();
		}

		return inStr;
	}
	
	public static String downloadPageNEncoding(final URL url, final boolean redirects, final Proxy proxy, final String useragent) throws Exception {
		final HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
		conn.setFollowRedirects(redirects);
		conn.setRequestProperty("Accept-Encoding", "iweb_enc, gzip, deflate");
		conn.setRequestProperty("User-Agent", useragent);
		final String encoding = conn.getContentEncoding();
		InputStream inStr = null;

		if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
		    inStr = new GZIPInputStream(conn.getInputStream());
		}else if (encoding != null && encoding.equalsIgnoreCase("deflate")) {
		    inStr = new InflaterInputStream(conn.getInputStream(),
		      new Inflater(true));
		}else {
		    inStr = conn.getInputStream();
		}

		//String out = IOUtils.getStringFromInputStream(inStr);
		byte[] outb = IOUtils.toByteArray(inStr);
		if (encoding != null && encoding.equalsIgnoreCase("iweb_enc")) {
			outb = EncryptAPI.getEncryptorForEncryption("XXTEA").decryptBytes(outb, 
					conn.getHeaderField("iWebKit.decryptKey"));
		}
		String out = new String(outb, CharsetUtil.UTF_8);
		if (!UnicodeUtils.hasUTF16Chars(out)) {
			out = new String(outb, CharsetUtil.UTF_16);
		}
		conn.disconnect();
		return out;
	}
	
	public static String downloadPage(final URL url, final boolean redirects, final Proxy proxy) throws IOException {
		return downloadPage(url, redirects, proxy, URIUtil2.URI_UTIL2_USER_AGENT, CharsetUtil.UTF_8);
	}
	
	public static String downloadPage(final URL url, final boolean redirects, final Proxy proxy,
			final String useragent) throws IOException {
		return downloadPage(url, redirects, proxy, useragent, CharsetUtil.UTF_8);
	}

	public static String downloadPage(final URL url, final boolean redirects, final Proxy proxy,
			final String useragent, final Charset charset) throws IOException {
		final HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
		conn.setFollowRedirects(redirects);
		conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
		conn.setRequestProperty("User-Agent", useragent);
		conn.setRequestProperty("Accept-Charset", charset.name());
		conn.setConnectTimeout(10000);
		final String encoding = conn.getContentEncoding();
		InputStream inStr = null;

		if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
		    inStr = new GZIPInputStream(conn.getInputStream());
		} else if (encoding != null && encoding.equalsIgnoreCase("deflate")) {
		    inStr = new InflaterInputStream(conn.getInputStream(),
		      new Inflater(true));
		} else {
		    inStr = conn.getInputStream();
		}

		final String out = IOUtils.getStringFromInputStream(inStr, charset);
		conn.disconnect();
		return out;
	}
	
	public static String downloadPageS(final URL url) {
		try {
			return downloadPage(url);
		}catch (Exception e) {
			e.printStackTrace();
			return "<html><head><h3 style='font-family:sans-serif;'>URIUtil had an error when downloading the page.</h3></head></html>";
		}
	}
	
	public static String downloadPageS(final URL url, final String userAgent) {
		try {
			return downloadPage(url, true, Proxy.NO_PROXY, userAgent);
		}catch (Exception e) {
			e.printStackTrace();
			return "<html><head><h3 style='font-family:sans-serif;'>URIUtil had an error when downloading the page.</h3></head></html>";
		}
	}

	public static boolean verifyPage(final URL url) {
		return verifyPage(url, Proxy.NO_PROXY);
	}

	public static boolean verifyPage(final URL url, final Proxy proxy) {
		return verifyPage(url, proxy, 1000);
	}

	public static boolean verifyPage(final URL url, final Proxy proxy, final int wait) {
		try {
			final HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
			conn.setFollowRedirects(true);
			conn.setConnectTimeout(wait);
			conn.setReadTimeout(wait);
			conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
			final String encoding = conn.getContentEncoding();
			InputStream inStr = null;

			if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
			    inStr = new GZIPInputStream(conn.getInputStream());
			} else if (encoding != null && encoding.equalsIgnoreCase("deflate")) {
			    inStr = new InflaterInputStream(conn.getInputStream(),
			      new Inflater(true));
			} else {
			    inStr = conn.getInputStream();
			}

			final String out = IOUtils.getStringFromInputStream(inStr);
			conn.disconnect();
			if (out == null) {
				//System.out.println("verifyPage: false [out == null]");
				return false;
			}else {
				//System.out.println("verifyPage: true");
				return true;
			}
		}catch (Exception e) {
			//System.out.println("verifyPage: false [error]");
			return false;
		}
	}

	public static List<String> extractText(final String code) throws IOException {
		final StringReader r = new StringReader(code);
		final List<String> out = extractText(r);
		r.close();
		return out;
	}

	public static List<String> extractText(final Reader reader) throws IOException {
		final ArrayList<String> list = new ArrayList<String>();

		ParserDelegator parserDelegator = new ParserDelegator();
		ParserCallback parserCallback = new ParserCallback() {

		    public void handleText(final char[] data, final int pos) {
		    	list.add(new String(data));
		    }

		    public void handleStartTag(Tag tag, MutableAttributeSet attribute, int pos) { }
		    public void handleEndTag(Tag t, final int pos) {  }
		    public void handleSimpleTag(Tag t, MutableAttributeSet a, final int pos) { }
		    public void handleComment(final char[] data, final int pos) { }
		    public void handleError(final java.lang.String errMsg, final int pos) { }
		 };
		 parserDelegator.parse(reader, parserCallback, true);
		 return list;
	}

    private static final Pattern p = Pattern.compile("^"
            + "(((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}" // Domain name
            + "|"
            + "localhost" // localhost
            + "|"
            + "(([0-9]{1,3}\\.){3})[0-9]{1,3})" // Ip
            + ":"
            + "[0-9]{1,5}$"); // Port

    public static final Pattern IP = p;
	public static boolean isIP(final String text) {
		return p.matcher(text).matches();
	}

	public static String getDomainNameE(final String url) {
		try {
			return getDomainName(url);
		}catch (Exception e) {
			return String.valueOf(Long.MIN_VALUE);
		}
	}
	
	public static URL safeCreateEncoded(final String page) {
		return safeCreate(safeEncode(page));
	}

	public static URL safeCreate(final String page) {
		try {
			return new URL(page);
		}catch (Exception e) {
			try {
				return new URL("https://" + page);
			}catch (Exception ea) {
				try {
					return new URL("https://www." + page);
				}catch (Exception eax) {
				}
			}
		}
		return null;
	}

	public static String downloadPageSALT(final URL url) {
		try {
			final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setFollowRedirects(true);
			conn.setRequestProperty("User-agent", String.valueOf(MathUtil.nextRandomInt()));
			final String data = IOUtils.getStringFromInputStream(conn.getInputStream());
			conn.disconnect();
			return data;
		}catch (Exception e) {
			e.printStackTrace();
			return "<html><head><h3 style='font-family:sans-serif;'>URIUtil had an error when downloading the page.</h3></head></html>";
		}
	}

	public static boolean isValid(final URL url) {
		if (url == null) {
			return false;
		}
		return isValid(url.toExternalForm());
	}

	public static String getName(final String uri) {
		final String out = uri.substring(uri.lastIndexOf('/')+1, uri.length());
		return out;
	}
	
    private static final int BUFFER_SIZE = 4096;

	public static final String IP_PATTERN = "([0-9]{1,3}\\.){3}[0-9]{1,3}";
    
    /**
     * Downloads a file from a URL
     * @param fileURL HTTP URL of the file to be downloaded
     * @param saveDir path of the directory to save the file
     * @throws IOException
     */
    public static void downloadFile(String fileURL, String saveDir)
            throws IOException {
        URL url = new URL(fileURL);
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
        int responseCode = httpConn.getResponseCode();
 
        // always check HTTP response code first
        if (responseCode == HttpURLConnection.HTTP_OK) {
            String fileName = "";
            String disposition = httpConn.getHeaderField("Content-Disposition");
            String contentType = httpConn.getContentType();
            int contentLength = httpConn.getContentLength();
 
            if (disposition != null) {
                // extracts file name from header field
                int index = disposition.indexOf("filename=");
                if (index > 0) {
                    fileName = disposition.substring(index + 10,
                            disposition.length() - 1);
                }
            } else {
                // extracts file name from URL
                fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1,
                        fileURL.length());
            }
 
            System.out.println("Content-Type = " + contentType);
            System.out.println("Content-Disposition = " + disposition);
            System.out.println("Content-Length = " + contentLength);
            System.out.println("fileName = " + fileName);
 
            // opens input stream from the HTTP connection
            InputStream inputStream = httpConn.getInputStream();
            String saveFilePath = saveDir + File.separator + fileName;
             
            // opens an output stream to save into file
            FileOutputStream outputStream = new FileOutputStream(saveFilePath);
 
            int bytesRead = -1;
            byte[] buffer = new byte[BUFFER_SIZE];
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
 
            outputStream.close();
            inputStream.close();
 
            System.out.println("File downloaded");
        } else {
            System.out.println("No file to download. Server replied HTTP code: " + responseCode);
        }
        httpConn.disconnect();
    }
    
    public static String queryStringFromMap(Map<String, String> map) throws UnsupportedEncodingException {
        StringBuilder encodedData = new StringBuilder();
        Iterator<Map.Entry<String, String>> mapIterator = map.entrySet().iterator();
        while (mapIterator.hasNext()) {
            Map.Entry<String, String> entry = mapIterator.next();
            if (mapIterator.hasNext()) {
                encodedData.append(String.format("%s&", urlEncodeEntry(entry)));
            } else {
                encodedData.append(urlEncodeEntry(entry));
            }
        }
        return encodedData.toString();
    }

    public static Map<String, String> queryStringToMap(String query) {
        HashMap<String, String> hashMap = new HashMap<String, String>();
        String[] encodedPairs = query.split("\\?|&");
        for (String pair : encodedPairs) {
            String[] splitPair = pair.split("=", -1);
           // if (splitPair.length != 2) {
            //	ArrayUtils.printLln(splitPair);
           //     throw new RuntimeException("query string not properly formatted [got=" + splitPair.length + ",expected=2]");
           // }

            String encodedKey = splitPair[0];
            String encodedValue = ArrayUtils.StringArrayUtils.addFrom(splitPair, 1, '=');
            hashMap.put(URIUtil.safeDecode(encodedKey), URIUtil.safeDecode(encodedValue));
        }
        return hashMap;
    }
    
    public static Map<String, String> queryStringToMap_NoDecode(String query) {
        HashMap<String, String> hashMap = new HashMap<String, String>();
        String[] encodedPairs = query.split("\\?|&");
        for (String pair : encodedPairs) {
            String[] splitPair = pair.split("=", -1);
           // if (splitPair.length != 2) {
            //	ArrayUtils.printLln(splitPair);
           //     throw new RuntimeException("query string not properly formatted [got=" + splitPair.length + ",expected=2]");
           // }

            String encodedKey = splitPair[0];
            String encodedValue = ArrayUtils.StringArrayUtils.addFrom(splitPair, 1, '=');
            hashMap.put(encodedKey, encodedValue);
        }
        return hashMap;
    }

    public static Map<String, String> queryStringToMap(String query, Boolean decode) throws UnsupportedEncodingException {
        HashMap<String, String> hashMap = new HashMap<String, String>();
        String[] encodedPairs = query.split("&");
        for (String pair : encodedPairs) {
            String[] splitPair = pair.split("=", -1);
            if (splitPair.length != 2) {
                throw new RuntimeException("query string not properly formatted");
            }

            String encodedKey = splitPair[0];
            String encodedValue = splitPair[1];
            if (decode) {
                hashMap.put(URLDecoder.decode(encodedKey, "utf-8"), URLDecoder.decode(encodedValue, "utf-8"));
            } else {
                hashMap.put(encodedKey, encodedValue);
            }
        }
        return hashMap;
    }

    public static String urlEncodeEntry(Map.Entry<String, String> entry) throws UnsupportedEncodingException {
        String encodedEntry = String.format("%s=%s",
                URLEncoder.encode(entry.getKey(), "utf-8"),
                URLEncoder.encode(entry.getValue(), "utf-8"));
        return encodedEntry;
    }

	public static int getResponseCode(final URL url) {
		try {
			final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setRequestProperty("User-agent", String.valueOf(MathUtil.nextRandomInt()));
			conn.setFollowRedirects(true);
			final int resp = conn.getResponseCode();
			return resp;
		}catch (Exception e) {
			return 404;
		}
	}
//
//	public static HTTPGet util2_gen_get_request(final HTTPGet out, final URL url, final String ua) {
//		out.local_set_resultR(getResponseCode(url), 0x0000D);
//		out.local_set_result(downloadPageS(url, ua), 0x0000D);
//		return out;
//	}

	public static String downloadPageSi(final URL url, final ErrorCallback callback) {
		try {
			return downloadPage(url);
		}catch (Exception e) {
			callback.onError(e);
			return "<html><head><h3 style='font-family:sans-serif;'>URIUtil had an error when downloading the page.</h3></head></html>";
		}
	}

	public static boolean isDomain(final String url) {
		try {
			final URI uri = new URI(url);
			final String domain = uri.getHost();
			return (domain.startsWith("www.") ? domain.substring(4) : domain) != null;
		}catch (Throwable t) {
			return false;
		}
	}

	public static boolean hasContent(final URL url) {
		try {
			final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setRequestProperty("User-Agent", URIUtil2.URI_UTIL2_USER_AGENT);
			conn.connect();
			final long length = conn.getContentLengthLong();
			final int resp = conn.getResponseCode();
			//System.out.println("check: length=" + length + ",resp=" + resp);
			conn.disconnect();
			return MathUtil.between(resp, 200, 299) && length > 0;
		}catch (Exception e) {
			return false;
		}
	}

	public static String safeDecode(final String string) {
		try {
			return URLDecoder.decode(string, CharsetUtil.UTF_8.name());
		}catch (Exception e) {
			try {
				return URLDecoder.decode(string);
			}catch (Exception e0) {
				return string;
			}
		}
	}

	public static String safeEncode(final String string) {
		try {
			return URLEncoder.encode(string, CharsetUtil.UTF_8.name());
		}catch (Exception e) {
			try {
				return URLEncoder.encode(string);
			}catch (Exception e0) {
				return string;
			}
		}
	}	
}
